package cn.devzyh.oms.wsms.service.impl;

import cn.devzyh.oms.common.core.constant.CacheConstants;
import cn.devzyh.oms.common.core.message.WsmsErrorCodes;
import cn.devzyh.oms.common.core.util.R;
import cn.devzyh.oms.common.core.util.RedisUtils;
import cn.devzyh.oms.wsms.api.constant.StockConstants;
import cn.devzyh.oms.wsms.api.dto.MatchWarehouseDTO;
import cn.devzyh.oms.wsms.api.entity.Stock;
import cn.devzyh.oms.wsms.api.entity.Warehouse;
import cn.devzyh.oms.wsms.mapper.StockMapper;
import cn.devzyh.oms.wsms.mapper.WarehouseMapper;
import cn.devzyh.oms.wsms.service.WarehouseService;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.List;
import java.util.Set;

@Slf4j
@Service
@RequiredArgsConstructor
public class WarehouseServiceImpl extends ServiceImpl<WarehouseMapper, Warehouse> implements WarehouseService {

	private final StockMapper stockMapper;

	/**
	 * 地球平均半径，单位为千米
	 */
	private static final double EARTH_RADIUS = 6371.0;


	@Cacheable(value = CacheConstants.WAREHOUSE_DETAIL, key = "#id")
	@Override
	public Warehouse getById(Serializable id) {
		return super.getById(id);
	}

	@CacheEvict(value = CacheConstants.WAREHOUSE_DETAIL, key = "#entity.code", condition = "#result")
	@Override
	public boolean save(Warehouse entity) {
		return super.save(entity);
	}

	@Override
	public boolean removeBatchByIds(Collection<?> list) {
		boolean res = super.removeBatchByIds(list);
		if (res) {
			RedisUtils.batchClearSpringCache(CacheConstants.CHANNEL_DETAIL, list);
		}
		return res;
	}

	@CacheEvict(value = CacheConstants.WAREHOUSE_DETAIL, key = "#entity.code", condition = "#result")
	@Override
	public boolean updateById(Warehouse entity) {
		return super.updateById(entity);
	}

	@Override
	public R<String> match(MatchWarehouseDTO dto) {
		List<Warehouse> warehouses = list();
		String warehouseCode = dto.getWarehouseCode();

		// 手动指定
		if (StrUtil.isNotBlank(warehouseCode)) {
			if (checkStock(warehouseCode, dto.getSkuCodes())) {
				return R.ok(warehouseCode);
			} else {
				return R.failedMessage(WsmsErrorCodes.MATCH_WAREHOUSE_FAILED, warehouseCode);
			}
		}

		// 自动匹配
		warehouses = CollectionUtil.sort(warehouses, (o1, o2) ->
				(int) calculateDistance(o1.getLatitude(), o1.getLongitude(), o2.getLatitude(), o2.getLongitude()));

		for (Warehouse warehouse : warehouses) {
			if (checkStock(warehouse.getCode(), dto.getSkuCodes())) {
				warehouseCode = warehouse.getCode();
				break;
			}
		}

		if (StrUtil.isBlank(warehouseCode)) {
			return R.failedMessage(WsmsErrorCodes.MATCH_WAREHOUSE_FAILED);
		}

		return R.ok(warehouseCode);
	}

	/**
	 * 计算两地距离差
	 *
	 * @param lat1
	 * @param lon1
	 * @param lat2
	 * @param lon2
	 * @return
	 */
	public static double calculateDistance(BigDecimal lat1, BigDecimal lon1, BigDecimal lat2, BigDecimal lon2) {
		// 将经纬度转换为弧度
		double radLat1 = Math.toRadians(lat1.doubleValue());
		double radLon1 = Math.toRadians(lon1.doubleValue());
		double radLat2 = Math.toRadians(lat2.doubleValue());
		double radLon2 = Math.toRadians(lon2.doubleValue());

		// 应用Haversine公式计算两个经纬度之间的距离
		double dLon = radLon2 - radLon1;
		double dLat = radLat2 - radLat1;
		double a = Math.pow(Math.sin(dLat / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(dLon / 2), 2);
		double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

		return EARTH_RADIUS * c;
	}

	/**
	 * 检查库存
	 *
	 * @param warehouseCode
	 * @param skuCodes
	 * @return
	 */
	boolean checkStock(String warehouseCode, Set<String> skuCodes) {
		// 库存检查
		for (String skuCode : skuCodes) {
			LambdaQueryWrapper<Stock> wrapper = Wrappers.lambdaQuery();
			wrapper.eq(Stock::getWarehouseCode, warehouseCode);
			wrapper.eq(Stock::getLocation, StockConstants.Location.AVAILABLE);
			wrapper.eq(Stock::getSkuCode, skuCode);
			if (!stockMapper.exists(wrapper)) {
				return false;
			}
		}

		return true;
	}

}